package Physics;

import java.util.Vector;

public class PhysicsEngine
{
public
	// for simplicity of coding
	//typedef std::vector<CollisionPrimitive > Objects;
	//typedef std::vector<ContactGenerator > ContactGenerators;
	//typedef std::vector<ParticleEffect > ParticleEffects;

	// keeps track of particles in the system
	//ParticleEffects particleEffects;

	// keeps track of the program's objects and
	// other classes that inherit from them
	Vector<CollisionPrimitive> m_objects;

	// keeps track of whether the user supplied
	// a value for the iterations limit. If not, we
	// just use 2x the number of contacts in the first
	// round.
	boolean m_calculateIterations;

	// the force generators for the objects
	ForceRegistry m_registry;

	// global forces (e.g. gravity, explosions etc.)
	public GlobalForceRegistry m_globalRegistry;

	// the contact resolver
	ContactResolver m_resolver;

	// the list we use to keep the contacts
	Vector<Contact> m_contacts;

	// the max number of contacts allowed (size
	// of the array)
	int m_maxContacts;

	// CollisionData object which stores relevant
	// collision information
	CollisionData m_cData;

	// creates a new particle world, which simulates
	// a world of particle objects that behave under
	// newtonian laws of physics.
	public PhysicsEngine(int maxContacts, int iterations)
	{
		// create a new array of Contact objects, based
		// on the max number of contacts we allow
		m_maxContacts = maxContacts;
		//m_iterations = iterations;
		
		m_cData = new CollisionData();
		m_cData.reset(maxContacts);
		m_resolver = new ContactResolver(iterations, 0.01f, 0.01f);
		m_globalRegistry = new GlobalForceRegistry();
		m_registry = new ForceRegistry();
		m_objects = new Vector<CollisionPrimitive>();
		m_contacts = new Vector<Contact>();

		m_cData.m_contactArray = m_contacts;

		m_globalRegistry.setObjectList(m_objects);

		// if the number of iterations is zero (i.e. we
		// didn't ask for a specific number and the default
		// argument took over) then set this boolean which
		// is used later
		if (iterations == 0)
			m_calculateIterations = true;
		else
			m_calculateIterations = false;
	}


	// generates the contacts
	public void generateContacts() {
	    // Set up the collision data structure
	    m_cData.reset(m_maxContacts);
	    m_cData.m_friction = 0.8f;
	    m_cData.m_restitution = 0.45f;

		// go through every pair of objects, determine what types of objects they are
		// and run the corresponding contact generation function.
	    for(int i = 0; i < m_objects.size(); ++i)
	    {
	    	CollisionPrimitive p = m_objects.get(i); 
	    	PrimitiveType A = p.getPrimitiveType();
	    	
	    	for (int j = i+1; j < m_objects.size(); j++)
	    	{
	    		if (!m_cData.hasMoreContacts())
	    			return;
	    		
	    		CollisionPrimitive x = m_objects.get(j);
	    		PrimitiveType B = x.getPrimitiveType();
	    		
	    		// do something different depending on the types of primitives.
	    		
	    		// check if both objects are activated. If one is deactivated, skip this step
	    		if (p.m_body != null && x.m_body != null)
	    		{
	    			if (!p.m_body.getActivated() || !x.m_body.getActivated())
	    			{
	    				continue;
	    			}
	    		}
	    		
				// needs excessive typecasting to convert from a Primitive to the correct
				// object pointer, then dereference it
				if		(A == PrimitiveType.PRIMITIVETYPE_COLLISIONSPHERE &&
							B == PrimitiveType.PRIMITIVETYPE_COLLISIONSPHERE)
				{
					CollisionDetector.sphereAndSphere((CollisionSphere)p, (CollisionSphere)x, m_cData);
				}			
				else if (A == PrimitiveType.PRIMITIVETYPE_COLLISIONSPHERE &&
							B == PrimitiveType.PRIMITIVETYPE_COLLISIONPLANE)
				{
					CollisionDetector.sphereAndHalfSpace((CollisionSphere)p, (CollisionPlane)x, m_cData);
				}			
				else if (A == PrimitiveType.PRIMITIVETYPE_COLLISIONSPHERE &&
							B == PrimitiveType.PRIMITIVETYPE_COLLISIONBOX)
						// need to flip the objects around
				{
					CollisionDetector.boxAndSphere((CollisionBox)x, (CollisionSphere)p, m_cData);
				}
				else if (A == PrimitiveType.PRIMITIVETYPE_COLLISIONPLANE &&
							B == PrimitiveType.PRIMITIVETYPE_COLLISIONSPHERE)
							// need to flip the objects around
				{
					CollisionDetector.sphereAndHalfSpace((CollisionSphere)x, (CollisionPlane)p, m_cData);
				}
				else if (A == PrimitiveType.PRIMITIVETYPE_COLLISIONPLANE &&
							B == PrimitiveType.PRIMITIVETYPE_COLLISIONBOX)
							// need to flip the objects around
				{
					if (x.m_body.getActivated())
					{
						CollisionDetector.boxAndHalfSpace((CollisionBox)x, (CollisionPlane)p, m_cData);
					}
				}
				else if (A == PrimitiveType.PRIMITIVETYPE_COLLISIONBOX &&
							B == PrimitiveType.PRIMITIVETYPE_COLLISIONSPHERE)
				{
					CollisionDetector.boxAndSphere((CollisionBox)p, (CollisionSphere)x, m_cData);
				}			
				else if (A == PrimitiveType.PRIMITIVETYPE_COLLISIONBOX &&
							B == PrimitiveType.PRIMITIVETYPE_COLLISIONPLANE)
				{		 
					if (p.m_body.getActivated())
					{
						CollisionDetector.boxAndHalfSpace((CollisionBox)p, (CollisionPlane)x, m_cData);
					}
				}
				else if (A == PrimitiveType.PRIMITIVETYPE_COLLISIONBOX &&
							B == PrimitiveType.PRIMITIVETYPE_COLLISIONBOX)
				{	
					CollisionDetector.boxAndBox((CollisionBox)p, (CollisionBox)x, m_cData);	
				}
				
			}
		}
		
	}

	// integrates all Particles
	public void integrate(float dt) {
		
		for (int i = 0; i < m_objects.size(); ++i)
		{
			CollisionPrimitive p = m_objects.get(i);
			// failsafe to make sure we don't try to clear accumulators for
			// primitive objects with no body e.g. CollisionPlanes
			if (p.m_body != null)
			{
				p.m_body.zeroZAxis();// our game plays on a 2D XY plane, so zero everything related to the Z-axis
				p.m_body.integrate(dt);
				p.calculateInternals();
			}
		}
		
		
/*
		ParticleEffects::iterator x = particleEffects.begin();
		while (x != particleEffects.end())
		{
			 x.Update(dt);
			x++;
		}		*/
	}
	
	// process physics
	public void runPhysics(float dt) {
		
		// apply global forces
		m_globalRegistry.updateForces(dt);

		// apply the forces
		m_registry.updateForces(dt);
		
		// integrate the objects
		integrate(dt);

		// generate contacts
		generateContacts();

		// resolve the contacts
		m_resolver.resolveContacts(m_cData.m_contactArray, dt);		
	}

	// ends the frame for the next iteration
	public void endFrame(){
		for (int i = 0; i < m_objects.size(); ++i)
		{
			CollisionPrimitive p = m_objects.get(i);
			// failsafe to make sure we don't try to clear accumulators for
			// primitive objects with no body e.g. CollisionPlanes
			if ( p.m_body != null)
			{
				 p.m_body.clearAccumulators();
				 p.m_body.calculateDerivedData();
			}
		}

		/*ParticleEffects::iterator x = particleEffects.begin();
		while (x != particleEffects.end())
		{
			 x.EndFrame();
			x++;
		}*/
	}

	// called each time through the main loop, to update
	// all of the program's objects
	public void update(float dt) {
		// apply forces etc.
		runPhysics(dt);

		// clear accumulators
		endFrame();
	}

	// gets the list of particles
	Vector<CollisionPrimitive>  getObjects(){
		return m_objects;		
	}

	// gets the force registry
	ForceRegistry  getForceRegistry(){
		return m_registry;		
	}

	// gets the global force registry
	GlobalForceRegistry  getGlobalForceRegistry(){
		return m_globalRegistry;		
	}

	// adds an object to the engine
	public void addObject(CollisionPrimitive  object){
		m_objects.add(object);		
	}

	// removes an object from the game
	public void RemoveObject(RigidBody  object){
		for (int i = 0; i < m_objects.size(); ++i)
		{
			CollisionPrimitive p = m_objects.get(i);
			if (p.m_body == object)
			{
				m_registry.remove(p.m_body);
				m_objects.remove(i);
				return;
			}
			else
			{
				i++;
			}
		}		
	}

	// removes all objects from the game
	public void RemoveAllObjects()
	{
		for (int i = 0; i < m_objects.size(); ++i)
		{
			CollisionPrimitive p = m_objects.get(i);
			m_registry.remove(p.m_body);
			m_objects.remove(i);
		}	
	}

	// checks if this objects has a collision to resolve in this iteration
	boolean HasCollided(RigidBody  object)
	{
		
		for (int i = 0; i < m_cData.m_contactArray.size(); i++)
		{
			Contact c = m_cData.m_contactArray.get(i);
			// if either object is this object, return true
			if (c.m_objects[0] == object || c.m_objects[1] == object)
				return true;

		}
		// if we didn't find it, return false
		return false;		
	}

	// adds a particle effect to the engine
	/*public void AddParticleEffect(ParticleEffect  particleEffect){
		particleEffects.push_back(particleEffect);		
	}*/

	// removes a particle effect from the engine
	/*public void RemoveParticleEffect(ParticleEffect  particleEffect){
		ParticleEffects::iterator i = particleEffects.begin();
		while (i != particleEffects.end())
		{
			if (i == particleEffect)
			{
				delete  i;
				particleEffects.erase(i);
				return;
			}
			else
			{
				i++;
			}
		}		
	}*/

	// removes all particlesEffects
	/*public void RemoveAllParticleEffects(void){
		ParticleEffects::iterator i = particleEffects.begin();
		while (i != particleEffects.end())
		{
			delete i;
			i = particleEffects.erase(i);
		}		
	}*/
}